Skip to content

Add CVSS4 support #12751

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: dev
Choose a base branch
from
Open

Conversation

valentijnscholten
Copy link
Member

@valentijnscholten valentijnscholten commented Jul 6, 2025

Fixes #12445

  • add cvssv4 and cvss4_score fields to Finding model
  • add score calculation/sanitization similar to what was done for cvssv3 field
  • update UI to show both vectors when present
  • update auditjs parser + tests
  • update parse_cvss_data to accomodate more use cases
  • add links to external calculators to relevant forms
  • update how to write a parser guide
  • add system_settings to allow hiding of cvssv3 fields or cvssv4 fields

vectors

image

QUESTIONS:
- Is it right to have the fields separated so we can support v3 anv v4 in parallel?
- Or should we converge this into cvss, cvss_score and cvss_version and prefer v4 over v3 if both are provided? This could be a problem for companies still tied to v3 in their compliance asessments?
- We could do both. Leave the PR as is, but also add these "merged" fields where we take the values from v4 if present else from v3 (or None otherwise)
- Do we want to embed a JS calculator again? The First/RedHat calculator is Vue based. The metaeffekt calculator is standalone JS:
- https://www.first.org/cvss/calculator/4-0
- https://www.metaeffekt.com/security/cvss/calculator/

@github-actions github-actions bot added New Migration Adding a new migration file. Take care when merging. unittests parser docker settings_changes Needs changes to settings.py based on changes in settings.dist.py included in this PR docs integration_tests ui labels Jul 6, 2025
@github-actions github-actions bot removed docker settings_changes Needs changes to settings.py based on changes in settings.dist.py included in this PR docs integration_tests labels Jul 7, 2025
@Maffooch
Copy link
Contributor

Maffooch commented Jul 7, 2025

QUESTIONS:

  • Is it right to have the fields separated so we can support v3 anv v4 in parallel?
  • Or should we converge this into cvss, cvss_score and cvss_version and prefer v4 over v3 if both are provided? This could be a problem for companies still tied to v3 in their compliance asessments?
  • We could do both. Leave the PR as is, but also add these "merged" fields where we take the values from v4 if present else from v3 (or None otherwise)

I think it would best to keep the two fields separate for now. As you mentioned, there are likely some companies that are reliant on v3 for compliance purposes. We also want to avoid potentially breaking changes in existing automation.

Add UI calculator

While I don't love the idea of embedding another big snippet of html on the view finding page, it would be consistent with the current behavior of v3. Being consistent would be ideal, but I am not sure it is worth the cost. Another option would be to link out to that universal calculator for both calculators. @mtesauro what do you think?

@valentijnscholten
Copy link
Member Author

I also considered linking out, but some companies might not like some external services being accessed. We could also link out to our own copy of that UI calculator hosted from within Defect Dojo. We could get rid of the old ugly one and just open the new one in a window. We could start with that and then see if there is a need for more integration like auto transfer for vectors between screens and whatnot. That would be the easiest for the Pro UI as well.

@github-actions github-actions bot added the docs label Jul 7, 2025
@valentijnscholten valentijnscholten added this to the 2.49.0 milestone Jul 7, 2025
@mtesauro
Copy link
Contributor

mtesauro commented Jul 8, 2025

QUESTIONS:

  • Is it right to have the fields separated so we can support v3 anv v4 in parallel?
  • Or should we converge this into cvss, cvss_score and cvss_version and prefer v4 over v3 if both are provided? This could be a problem for companies still tied to v3 in their compliance asessments?
  • We could do both. Leave the PR as is, but also add these "merged" fields where we take the values from v4 if present else from v3 (or None otherwise)

I think it would best to keep the two fields separate for now. As you mentioned, there are likely some companies that are reliant on v3 for compliance purposes. We also want to avoid potentially breaking changes in existing automation.

I agree with this as well

Add UI calculator

While I don't love the idea of embedding another big snippet of html on the view finding page, it would be consistent with the current behavior of v3. Being consistent would be ideal, but I am not sure it is worth the cost. Another option would be to link out to that universal calculator for both calculators. @mtesauro what do you think?

It's probably best to include whatever we what displayed by default in DefectDojo in the source code aka shipped in our containers as we cannot rely on outbound connectivity for any default feature of DefectDojo.
So, I see the options as:

  1. Follow the same process as CVSSv3 and include the code for the calculator in our containers/source
  2. Default to no CVSSv4 calculator and have people opt-in to having it call out to an external resource. This resource would default to First's calculator but allow people to update to a different URL should they choose or need to because of network restrictions, policy, whatever.

Other thoughts on the CVSS calculators:

  • It makes sense to me to have both v3 and v4 be optionally displayed. As much we have plenty of System Settings, this makes sense to me especially as we start to deprecate v3. This is something we should do as we adopt v4.
  • In an ideal world, both calculators would be overlays/pop-out UIs that don't busy up the edit finding UI any more then they currently are but that's more of an 'icing on the cake' and maybe we bake the v4 cake first. That nicer UI could be some follow-on work that someone in the community with good UI foo can tackle.

@valentijnscholten valentijnscholten marked this pull request as ready for review July 9, 2025 20:06
Copy link

dryrunsecurity bot commented Jul 9, 2025

DryRun Security

🟡 Please give this pull request extra attention during review.

This pull request contains multiple security findings including a potential ReDoS vulnerability in CVSS vector parsing, debug logging that might expose sensitive information, a command injection risk in a shell script, and a potential cross-site scripting (XSS) vulnerability in the BulletListDisplayWidget class, with the XSS issue being marked as risky.

🟡 Potential Cross-Site Scripting in dojo/forms.py
Vulnerability Potential Cross-Site Scripting
Description The code is potentially vulnerable to XSS in the BulletListDisplayWidget class's render method. While the method uses mark_safe(), which suggests some safety, it directly interpolates the url and text values from the urls_dict into the HTML without explicit sanitization. If an attacker can control the urls_dict input, they could potentially inject malicious JavaScript or HTML. The use of f'<li style="list-style-type: disc;"><a href="{url}" target="_blank"><i class="fa fa-arrow-up-right-from-square" style="margin-right: 5px;"></i>{text}</a></li>' creates an opportunity for XSS by directly embedding unsanitized user-supplied values into the HTML structure.

EFFORT_FOR_FIXING_INVALID_CHOICE = _("Select valid choice: Low,Medium,High")
class BulletListDisplayWidget(forms.Widget):
def __init__(self, urls_dict=None, *args, **kwargs):
self.urls_dict = urls_dict or {}
super().__init__(*args, **kwargs)
def render(self, name, value, attrs=None, renderer=None):
if not self.urls_dict:
return ""
html = '<ul style="margin: 0; padding-left: 20px;">'
for url, text in self.urls_dict.items():
html += f'<li style="list-style-type: disc;"><a href="{url}" target="_blank"><i class="fa fa-arrow-up-right-from-square" style="margin-right: 5px;"></i>{text}</a></li>'
html += "</ul>"
return mark_safe(html)
class MultipleSelectWithPop(forms.SelectMultiple):
def render(self, name, *args, **kwargs):
html = super().render(name, *args, **kwargs)

ReDoS in CVSS Vector Parsing in dojo/utils.py
Vulnerability ReDoS in CVSS Vector Parsing
Description The parse_cvss_from_text function uses a regular expression that could potentially be vulnerable to excessive backtracking with specially crafted input. Complex or long input strings might cause significant CPU resource consumption, leading to a potential Denial of Service.

import bleach
import crum
import hyperlink
import vobject
from asteval import Interpreter
from auditlog.models import LogEntry
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cvss import CVSS2, CVSS3, CVSS4, CVSSError
from dateutil.parser import parse
from dateutil.relativedelta import MO, SU, relativedelta
from django.conf import settings

Information Disclosure via Debug Logging in dojo/utils.py
Vulnerability Information Disclosure via Debug Logging
Description Debug logging in the CVSS parsing functions includes potentially sensitive information about CVSS vectors. If debug logging is enabled in a production environment, this could lead to unintended exposure of vulnerability details.

return response
# TEMPORARY: Local implementation until the upstream PR is merged & released: https://github.com/RedHatProductSecurity/cvss/pull/75
def parse_cvss_from_text(text):
"""
Parses CVSS2, CVSS3, and CVSS4 vectors from arbitrary text and returns a list of CVSS objects.
Parses text for substrings that look similar to CVSS vector
and feeds these matches to CVSS constructor.
Args:
text (str): arbitrary text
Returns:
A list of CVSS objects.
"""
# Looks for substrings that resemble CVSS2, CVSS3, or CVSS4 vectors.
# CVSS3 and CVSS4 vectors start with a 'CVSS:x.x/' prefix and are matched by the optional non-capturing group.
# CVSS2 vectors do not include a prefix and are matched by raw vector pattern only.
# Minimum total match length is 26 characters to reduce false positives.
matches = re.compile(r"(?:CVSS:[3-4]\.\d/)?[A-Za-z:/]{26,}").findall(text)
cvsss = set()
for match in matches:
try:
if match.startswith("CVSS:4."):
cvss = CVSS4(match)
elif match.startswith("CVSS:3."):
cvss = CVSS3(match)
else:
cvss = CVSS2(match)
cvsss.add(cvss)
except (CVSSError, KeyError):
pass
return list(cvsss)
def parse_cvss_data(cvss_vector_string: str) -> dict:
if not cvss_vector_string:
return {}
vectors = parse_cvss_from_text(cvss_vector_string)
if len(vectors) > 0:
vector = vectors[0]
# For CVSS2, environmental score is at index 2
# For CVSS3, environmental score is at index 2
# For CVSS4, only base score is available (at index 0)
# These CVSS2/3/4 objects do not have a version field (only a minor_version field)
major_version = cvssv2 = cvssv2_score = cvssv3 = cvssv3_score = cvssv4 = cvssv4_score = severity = None
if type(vector) is CVSS4:
major_version = 4
cvssv4 = vector.clean_vector()
cvssv4_score = vector.scores()[0]
logger.debug("CVSS4 vector: %s, score: %s", cvssv4, cvssv4_score)
severity = vector.severities()[0]
elif type(vector) is CVSS3:
major_version = 3
cvssv3 = vector.clean_vector()
cvssv3_score = vector.scores()[2]
severity = vector.severities()[0]
elif type(vector) is CVSS2:
# CVSS2 is not supported, but we return it anyway to allow parser to use the severity or score for other purposes
cvssv2 = vector.clean_vector()
cvssv2_score = vector.scores()[2]
severity = vector.severities()[0]
major_version = 2
return {
"major_version": major_version,
"cvssv2": cvssv2,
"cvssv2_score": cvssv2_score,
"cvssv3": cvssv3,
"cvssv3_score": cvssv3_score,
"cvssv4": cvssv4,
"cvssv4_score": cvssv4_score,
"severity": severity,
}
logger.debug("No valid CVSS3 or CVSS4 vector found in %s", cvss_vector_string)
return {}

Command Injection Risk in run-unittest.sh
Vulnerability Command Injection Risk
Description The script constructs shell commands using user-controlled variables TEST_CASE and FAIL_FAST without proper input sanitization. An attacker could potentially inject arbitrary shell commands by manipulating these parameters, leading to remote code execution within the Docker container.

#!/usr/bin/env bash
unset TEST_CASE
unset FAIL_FAST
bash ./docker/docker-compose-check.sh
if [[ $? -eq 1 ]]; then exit 1; fi


All finding details can be found in the DryRun Security Dashboard.

Copy link
Contributor

@Maffooch Maffooch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice job on this 😄

dojo/models.py Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blakeaowens for your awareness of new fields

@Maffooch Maffooch requested review from dogboat and blakeaowens July 11, 2025 23:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs integration_tests New Migration Adding a new migration file. Take care when merging. parser ui unittests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants